//=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|
// Classe Gnuplot
// ^^^^^^^^^^^^^^
//    Faz a interface entre programas C++ e o gnuplot.
//    Para isso, usa pipes, por isso, nao rodara em sistemas sem esse suporte.
//    O codigo foi inspirado da classe desenvolvida por:
//      Rajarshi Guha
//      <rajarshi@presidency.com>
// 
//________________________________________________________
//A.Z. - 01/06 => Criacao a partir da classe de Rajarshi Guha
//       02/06 => Usa tres variaveis para garantir nome unico dos arquivos temp.
//       03/06 => Consertei o bug que existia no template das funcoes range
//       03/06 => Funcoes plotEquation
//       04/06 => Funcoes multipleBoxes e multipleBoxes_origin
//       12/06 => Funcao para plotar pontos com barras de erro (pega 3 vectors)
//       01/07 => Suporte ao uso do mouse (Gnuplot 4.0 e superior)
//       01/07 => Funcao plot( MTcs &, MTcs & );
//       03/07 => Inclui a funcao findNearest
//       03/07 => Funcao plotUsing
//       03/07 => Funcao plot chama plot do gnuplot e replot chama replot
//       08/07 => Corrigi um bug em replotEquatio que causava um loop infinito
//       09/07 => Sobrecarga de operator<<
//                operator<< usa operator()
//                Opcao de passar uma string extra para operator()
//                Eliminei [x,y,z]range e [x,y,z]label. Use <<
//                Soh 1 funcao getMouse (retorna o botao clicado)
//                Eliminei as funcoes multipleBoxes e multipleBoxes_origin
//       11/07 => Funcao splot e variavel _splot pra fazer graficos com splot
//=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|
#ifndef GNUPLOT_H
#define GNUPLOT_H


#include <cmath>
#include <vector>
#include <string>
#include <sstream>
#include <unistd.h>
#include <iostream>
#include "numeros.hpp"
#include "file_uti.hpp"
#include "meusTipos.hpp"
using namespace std;



int findNearest( MTcvd &, MTcvd &, MTcd &, MTcd & );



class Gnuplot{
  template<class T> friend Gnuplot &operator << ( Gnuplot &, const T & );
public:
  Gnuplot(MTcs& = "lines" );
  ~Gnuplot(){
    pclose(_gnuplotProgram);
    _nobj--;
  }

  void operator()(string, MTcs & = "\n") const;

public:
  void plot( MTcs &, MTcs & = "" );
  template<class T>
    void plot( const vector<T>&, MTcs & = "" );
  template<class T1, class T2>
    void plot( const vector<T1>&, const vector<T2>&, MTcs & = "");
  template<class T1, class T2, class T3>
    void plot( const vector<T1>&, const vector<T2>&, const vector<T3>&, 
                                                             MTcs & = "");
  void plotEquation( MTcs &, MTcs & = "" );



  // As funcoes replot fazer a variavel replot true e depois chamam as
  // respectivas funcoes plot para fazerem o grafico
  void replot( MTcs &dataFile, MTcs &title = "" ){
    _replot=true; this->plot(dataFile,title); _replot=false;
  }
  template<class T>
  void replot( const vector<T>&x, MTcs &title = "" ){
    _replot=true; this->plot(x,title);  _replot=false;
  }
  template<class T1, class T2>
  void replot( const vector<T1>&x, const vector<T2>&y, MTcs &title = ""){
    _replot=true; this->plot(x,y,title);  _replot=false;
  }
  template<class T1, class T2, class T3>
  void replot( const vector<T1>&x, const vector<T2>&y, const vector<T3>&error,
                 MTcs &title = ""){
    _replot=true; this->plot(x,y,error,title);  _replot=false;
  }
  void replotEquation( MTcs &equation, MTcs &title = "" ){
    _replot=true; this->plotEquation(equation,title);  _replot=false;
  }


  void resetPlot(MTci& = 0 );
  bool splot( MTcb &splot ){
    _splot=splot;
    return _splot;
  }

public:
  inline void plotWith( MTcs& s, MTci& t=-1000 ){
    _pstyle = s;
    if( t!=-1000 ) _pstyle += " " + ntos(t);
  };

  inline void plotUsing( MTcs& u ){ _pusing = u; };

  inline int getMouse (double &, double &) const;

private:
  bool _splot;                // true quando eh pra plotar com splot
  bool _replot;               // true quando funcoes replot sao chamadas
  string  _pstyle;            // plot with ...
  string  _pusing;            // plot using ...
  FILE   *_gnuplotProgram;    // Coracao da classe. Eh o gnuplot aberto


  //   Estas variaveis controlam os arquivos temporarios criados pelas funcoes
  // plot.
  //   Eh preciso que cada arquivo de dados tenha seu nome proprio. Neste caso,
  // precisamos pensar em 3 situacoes:
  //   1) O usuario chama plot varias vezes seguidas no mesmo programa.
  //   2) O usuario instancia varios objetos desta classe, e cada um deles chama
  //      a funcao plot
  //   3) O usuario executa o mesmo programa em varios terminais diferentes,
  //      simultaneamente
  //   Para nos livrarmos da possibilidade de sobreescrever arquivos, precisamos
  // de tres variaveis:
  int      _nplots;  // Numero de plots chamados   ->   Nos livra do caso 1
  static int _nobj;  // Numero de objetos
  int    _nthisObj;  // Numero deste objeto        ->   Nos livra do caso 2
  string      _pid;  // Numero do processo         ->   Nos livra do caso 3
};
int Gnuplot::_nobj=0;




//------------------------------------------------------------------------------
// DESCRICAO:
//   Construtor
// RECEBE:
//   style => Estilo de linha
Gnuplot::Gnuplot( MTcs &styleStr ){
  // Cria um processo com o gnuplot
  _gnuplotProgram = popen("gnuplot","w");
  if(!_gnuplotProgram) aborta("Impossivel abrir o gnuplot!");

  // Seta variaveis
  this->plotWith(styleStr);
  _pusing = "";
  _nplots = 0;
  _pid = ntos( getpid() );
  _nobj++;
  _nthisObj = _nobj;

  _splot =false;
  _replot=false;

  //   Habilita o mouse antes de de qq plotagem por causa do que eh dito no
  // help x11_mouse:
  //   Mouse support is turned off if standard input does not come from a tty, 
  // e.g. a pipe. If you want to use mouse support while writing to gnuplot 
  // from a pipe, the mouse must be turned on *before* starting the x11 driver, 
  // e.g. immediately after startup with the explicit command `set mouse`.
  this->operator()("set mouse");
}




//------------------------------------------------------------------------------
// DESCRICAO:
//   Sobrecarga do operador << para mandar comandos para o gnuplot
// RECEBE:
//   gp     => A instancia atual do Gnuplot
//   cmdstr => O comando
template<class T>
Gnuplot &operator << ( Gnuplot &gp, const T &cmd ){

  // Converte o comando para uma string
  ostringstream cmdstr;
  cmdstr << cmd;
  gp( cmdstr.str(), "" );

  return gp;
}




//------------------------------------------------------------------------------
// DESCRICAO:
//   Envia um comando para o gnuplot
// RECEBE:
//   cmdstr => O comando
void Gnuplot::operator()( string cmdstr, MTcs &sep ) const {
  cmdstr += sep;
  fputs( cmdstr.c_str(), _gnuplotProgram );
  fflush( _gnuplotProgram );
}



//------------------------------------------------------------------------------
// DESCRICAO:
//   Plota os dados de um arquivo
// RECEBE:
//   dataFile => Arquivo de dados
//   title    => Titulo do grafico
void Gnuplot::plot( MTcs &dataFile, MTcs &title ){

  // Plota com: plot, replot ou splot??
  string cmdstr="plot ";
  if( _splot  ) cmdstr="splot ";
  if( _replot ) cmdstr="replot ";

  // Faz o grafico
  cmdstr                    += "\"" + dataFile + "\"";
  if(_pusing!="")    cmdstr += " using "+_pusing+" ";
  if(title.empty())  cmdstr += " notitle ";
  else               cmdstr += " title \""+title+"\"";
  cmdstr                    += " with " + _pstyle;

  this->operator()( cmdstr );
  _nplots++;  
}



//------------------------------------------------------------------------------
// DESCRICAO:
//   Plota o vector x usando a funcao para plotar 3 vectors
// RECEBE:
//   x     => Vector para plotar
//   title => Titulo do grafico
template<class T>
void Gnuplot::plot( const vector<T>& x, MTcs &title ){
  const vector<T> y(0),error(0);
  this->plot(x,y,error,title);
}




//------------------------------------------------------------------------------
// DESCRICAO:
//   Plota os vectors x e y usando a funcao para plotar 3 vectors
// RECEBE:
//   x,y   => Vectors para plotar
//   title => Titulo do grafico
template<class T1, class T2 >
void Gnuplot::plot( const vector<T1>& x, const vector<T2>& y, MTcs &title ){
  const vector<T1> error(0);
  this->plot(x,y,error,title);
}




//------------------------------------------------------------------------------
// DESCRICAO:
//   Plota os vectors x e y e error.
//   Cria um arquivo com os dados e depois chama a funcao plot(MTcs&,MTcs&)
// RECEBE:
//   x,y,error   => Vectors para plotar
//   title       => Titulo do grafico
template<class T1, class T2, class T3 >
void Gnuplot::plot( const vector<T1>& x, const vector<T2>& y,
                    const vector<T3>& error, MTcs &title ){

  // Cria o arquivo temporario
  MTcs tmp = "/tmp/fileOfGnuplotClass." + _pid + "." + ntos(_nthisObj) + "." +
                                                         ntos(_nplots) + ".dat";
  ofstream TMP(tmp.c_str());
  abriu( TMP, tmp );


  // Coloca os dados no arquivo
  MTci size = x.size();
  if( error.empty() ){                    // Se error estiver vazio
    if( y.empty() ){                      // Se y tambem estiver vazio
      for( int i=0; i<size; i++)          // soh plota x
        TMP << x[i] << "\n";
    }else{                                // Senao plota x e y
      if(size!=y.size()) aborta("Os vectors x e y nao tem o mesmo tamanho!");
      for( int i=0; i<size; i++)
        TMP << x[i] << " " << y[i] << "\n";
    }
  }else{                                // Caso contrario
    if(size!=y.size()) aborta("Os vectors x e y nao tem o mesmo tamanho!");
    if(size!=error.size())
      aborta("Os vectors x e error nao tem o mesmo tamanho!");
    for( int i=0; i<size; i++)
      TMP << x[i] << " " << y[i] << " " << error[i] << "\n";
  }
  flush(TMP);
  TMP.close(); 



  // Manda o gnuplot plotar
  this->plot( tmp, title );
}



//------------------------------------------------------------------------------
// DESCRICAO:
//   Plota uma equacao
// RECEBE:
//   equation => Equacao para plotar
//   title    => Titulo do grafico
void Gnuplot::plotEquation( MTcs &equation, MTcs &title ){

  // Manda o gnuplot plotar
  string cmdstr;
  if(_replot)        cmdstr  = "replot ";
  else               cmdstr  = "plot ";
  cmdstr                    += equation;
  if(title.empty())  cmdstr += " notitle ";
  else               cmdstr += " title \""+title+"\"";
  cmdstr                    += " with " + _pstyle;

  this->operator()( cmdstr );
  _nplots++;
}




//------------------------------------------------------------------------------
// DESCRICAO:
//   Se plot for chamado continuamente, vai sobrepondo os graficos.
//   resetPlot elimina os ultimos n graficos
// RECEBE:
//   n => Numero de graficos anteriores para eliminar
void Gnuplot::resetPlot( MTci& n ){
  if( n<=0 || n>=_nplots ) _nplots  = 0;
  else                     _nplots -= n;
}




//------------------------------------------------------------------------------
// DESCRICAO:
//   Le a posicao do mouse
// RECEBE:
//   button => Botao do mouse (soh funciona o 1 e o 2)
//   x,y    => Posicao x,y (no grafico)
inline int Gnuplot::getMouse (double &x, double &y) const{
  this->operator()("pause mouse");

  // Coloca as variaveis num arquivo para le-las
  MTcs tmp = "/tmp/fileOfGnuplotClass." + _pid + "." + "mouse.dat";
  this->operator()("set print \""+tmp+"\"");
  this->operator()("print MOUSE_BUTTON,MOUSE_X,MOUSE_Y");
  this->operator()("set print");

  //  Le as variaveis do arquivo
  //  Eh preciso ficar dando um loop ateh que o usuario clique com o mouse no
  // grafico, senao o controle do programa vai adiante antes que isso aconteca.
  // Como consequencia, o programa aborta.
  //  Nao sei porque, mas nao dah pra ler o arquivo com a mesma classe TMP. Eh
  // preciso abrir outra depois que eu tenho certeza que o arquivo jah foi
  // gerado pelo gnuplot e que os dados estao lah.
  ifstream TMP;
  do{
    TMP.open(tmp.c_str());
  }while(!TMP.is_open());
  TMP.close();
  ifstream TMP2(tmp.c_str());
  int button=0;
  TMP2 >> button >> x >> y;
  TMP2.close();

  // Deleta o arquivo gerado
  MTcs cmd = "rm "+tmp;
  system(cmd.c_str());  

  return button;
}







//------------------------------------------------------------------------------
//-- Esta funcao nao eh membro da classe Gnuplot -------------------------------
//------------------------------------------------------------------------------
// DESCRICAO:
//   O objetivo desta funcao eh dar suporte a funcao getMouse. Quando se pega
//  um valor de posicao do mouse, pode ser interessante saber qual eh ponto
//  no grafico que estah mais perto da posicao clicada. Eh exatamente isso que
//  essa funcao faz, retornando o indice deste ponto no vector.
// RECEBE:
//   vecX => Vector com os pontos x
//   vecY => Vector com os pontos y
//   mX   => Posicao x clicada com o mouse
//   mY   => Posicao y clicada com o mouse
// RETORNA:
//   Indice da posicao mais proxima do vector
int findNearest( MTcvd &vecX, MTcvd &vecY, MTcd &mX, MTcd &mY ){
  MTci size=vecX.size();
  if( size != vecY.size() ) aborta("Os vectors precisam ter o mesmo tamanho.");

  int index=-1;
  double min=1e300, daux;
  for( int i=0; i<size; i++ ){
    daux = hypot( vecX[i]-mX, vecY[i]-mY );
    if( daux < min ){
      min = daux;
      index = i;
    }
  }


  if(index==-1) aborta("Nao foi possivel encontrar o ponto!");

  return index;
}


#endif // GNUPLOT_H
